#!/bin/bash
# ----------------------------------------------------------------------------
# @file dstm
# Simple Drupal src tree manager
# @see color (http://code.google.com/p/ansi-color/)
# @author Alister Lewis-Bowen [alister@different.com]
# @modified 22-54_2008-Jun-12
# @todo Create list of possible Tags to check against
# ----------------------------------------------------------------------------
# This software is distributed under the the MIT License.
#
# Copyright (c) 2008 Alister Lewis-Bowen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ----------------------------------------------------------------------------

# Base directory of local Drupal source tree
# ----------------------------------------------------------------------------

BASE=${DSTM_BASE:-~/.dstm};

# Drupal CVS core repository
# ----------------------------------------------------------------------------

CVS_REPO=${DSTM_CVS_REPO:-'-d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal'};

# Drupal CVS contrib repository
# ----------------------------------------------------------------------------

CVS_CONTRIB_REPO=${DSTM_CVS_CONTRIB_REPO:-'-d:pserver:anonymous:anonymous@cvs.drupal.org:/cvs/drupal-contrib'};

# Drupal contrib module directory in the CVS repository
# ----------------------------------------------------------------------------

CVS_MODULE_REPO_DIR=${DSTM_CVS_MODULE_REPO_DIR:-contributions/modules};

# Drupal contrib theme directory in the CVS repository
# ----------------------------------------------------------------------------

CVS_THEME_REPO_DIR=${DSTM_CVS_THEME_REPO_DIR:-contributions/themes};

# Core location in the Drupal source tree
# ----------------------------------------------------------------------------

TREE_CORE=${DSTM_TREE_CORE:-$BASE/core};

# Contrib module location in the Drupal source tree
# ----------------------------------------------------------------------------

TREE_MODULE=${DSTM_TREE_MODULE:-$BASE/contrib/modules};

# Contrib themes location in the Drupal source tree
# ----------------------------------------------------------------------------

TREE_THEME=${DSTM_TREE_THEME:-$BASE/contrib/themes};

# Profile desciption of the Drupal source tree
# ----------------------------------------------------------------------------

PROFILE=${DSTM_PROFILE:-$BASE/profile};

# Error log location
# ----------------------------------------------------------------------------

ERROR_LOG=${DSTM_ERROR_LOG:-$BASE/error.log};

# Defined editor
# ----------------------------------------------------------------------------

EDITOR=${EDITOR:-vi};

# Time stamp format
# ----------------------------------------------------------------------------

TIMESTAMP=`date +"%H-%M_%Y-%b-%d"`;

# Function: Help
# ----------------------------------------------------------------------------

function help {
	clear;
	echo "$(color bd)NAME$(color)";
	echo -e "\tdstm -- simple Drupal source tree manager";
	echo;
	echo "$(color bd)SYNOPSIS$(color)";
	echo;
	echo "$(color bd)DESCRIPTION$(color)";
	echo;
	echo "$(color bd)EXAMPLES$(color)";
	echo;
	echo "$(color bd)ENVIRONMENT$(color)";
	echo;
	echo "$(color bd)AUTHOR$(color)";
	echo -e "\tWritten by Alister Lewis-Bowen";
	echo;
	echo "$(color bd)REPORTING BUGS$(color)";
	echo -e "\t Report bugs to <feedback@different.com>.";
	echo;
	echo "$(color bd)LICENSE$(color)";
	echo -e "\tCopyright (c) 2008 Alister Lewis-Bowen.";
	echo;
	echo -e "\tThis software is distributed under the the MIT License.";
	echo;
	echo "$(color bd)SEE ALSO$(color)";
	echo;
	exit 1;
}

# Function: Echo a status message
# Usage: status [<string>|done]
#				 status error <string>
# ----------------------------------------------------------------------------

function status() {
	case "$1" in
		error )
			echo -e "$(color red)Error:$(color) $2";
			exit 1;
			;;
		done )
			if [ -z "$QUIET" ]; then echo " $(color green)Done$(color)"; fi;
			;;
		* )
			if [ -z "$QUIET" ]; then echo -en "$1...\t"; fi;
			;;
	esac;
}

# Function: Display CVS Tag for a context within the Drupal source tree
# Usage: getCVStag [context] [branch] [project]
# ----------------------------------------------------------------------------

function getCVStag() {
	local _context=${1:-module};
	local _project=${3:-devel};
	local _branch=${2:-HEAD};
	local _tag;

	case "$_context" in
		core )
			_tag=`cat $TREE_CORE/$_branch/CVS/Tag`;;
		module )
			_tag=`cat $TREE_MODULE/$_branch/$_project/CVS/Tag`;;
		theme )
			_tag=`cat $TREE_THEME/$_branch/$_project/CVS/Tag`;;
	esac;
	
	echo -n ${_tag:1};
}

# Function: Display module description within the Drupal source tree
# Usage: getInfoFileDescription [context] [branch] [project] 
# ----------------------------------------------------------------------------

function getInfoFileDescription() {
	local _context=${1:-module};
	local _branch=${2:-HEAD};
	local _project=${3:-devel};
	local _dir;
	
	case "$_context" in
		core )		_dir=$TREE_CORE;;
		module )	_dir=$TREE_MODULE;;
		theme )		_dir=$TREE_THEME;;
	esac;
	
	local _desc=`find $_dir/$_branch/$_project -name $_project.info | xargs egrep "^description" | cut -f2 -d '=' | tr -d '\"'`;
	echo -n ${_desc:1};
}
# Function: Clean the local Drupal source tree
# ----------------------------------------------------------------------------

function clean() {
	echo -n "I am going to clean the source tree. ";
	echo "All the contents of $BASE will be deleted and a fresh empty tree structure will be created.";
	echo -n "$(color bd)Are you ready to continue (Yes|No) ? $(color off): ";
	read confirmation;
	if [ "$confirmation" != 'Yes' ]; then
		echo "$(color green)$(color bd)Cancelling$(color off)";
		exit 1;
	fi;
	echo;
	status "Cleaning the local source tree...";
	rm -fR $BASE;
	mkdir -p $TREE_CORE $TREE_MODULE $TREE_THEME;
	touch $ERROR_LOG;
	status done;
}

# Function: Build the local Drupal source tree
# ----------------------------------------------------------------------------

function build() {
	get core;
	get module;
	get theme;
}
# Function: Create a list of branches from the profile
# ----------------------------------------------------------------------------

function profileBranches() {
	if [[ -e $PROFILE && `cat $PROFILE | egrep ^IncludeBranch | wc -l | tr -d ' '` != '0' ]]; then
		local _index=0;
		for _branch in `cat $PROFILE | egrep ^IncludeBranch | awk {'print $2'}`; do
			BRANCHES[$_index]=$_branch;
			(( _index += 1 ));
		done;
	fi;
}

# Function: Create a list of include modules from the profile
# ----------------------------------------------------------------------------

function profileModules() {
	if [[ -e $PROFILE && `cat $PROFILE | egrep ^IncludeModule | wc -l | tr -d ' '` != '0' ]]; then
		local _index=0;
	 	for _project in `cat $PROFILE | egrep ^IncludeModule | awk {'print $2'}`; do
	 		MODULES[$_index]=$_project;
			(( _index += 1 ));
	 	done;
	fi;
}

# Function: Example data for a dstm profile
# Usage: profile [show|update|delete]
#        profile import file
#        profile export [file]
# ----------------------------------------------------------------------------

function profile() {
	local _action=${1:-show};
	local _file=${2-./dstm-$TIMESTAMP.profile};
	
	case "$_action" in
		sample )
			status "Creating sample profile...";
			if [ ! -e $PROFILE ]; then
				cat > $PROFILE <<_END_OF_SAMPLE_PROFILE_;
# ----------------------------------------------------------------------------
# @file Drupal source tree manager (dstm) Profile
# @created $TIMESTAMP
# @modified $TIMESTAMP
# ----------------------------------------------------------------------------

# INCLUDE BRANCHES TO THE SOURCE TREE
# ----------------------------------------------------------------------------
# Add the branches that should appear in the local Drupal source tree. By 
# default the HEAD branch will be built if nothing is defined here.
# e.g. IncludeBranch 6 
IncludeBranch 5
IncludeBranch 6

# INCLUDE MODULES WHEN BUILDING THE SOURCE TREE
# ----------------------------------------------------------------------------
# Add the modules that should be included for each branch defined above. These
# are checked out of CVS when the source tree is built. 
# e.g. IncludeModule devel
IncludeModule backup_migrate
IncludeModule devel
IncludeModule simplemenu
IncludeModule logintoboggan
IncludeModule pngfix

# EXCLUDE MODULES WHEN BUILDING THE SOURCE TREE 
# ----------------------------------------------------------------------------
# Define which of the modules defined about should not be included into a
# branch.
# e.g. ExcludeModule backup_migrate 6
ExcludeModule backup_migrate 6

# INCLUDE THEME WHEN BUILDING THE SOURCE TREE
# ----------------------------------------------------------------------------
# Add the themes that should be included for each branch defined above. These
# are checked out of CVS when the source tree is built. 
# e.g. IncludeTheme zen
IncludeTheme zen

# LOCK A MODULE TO A SPECIFIC VERSION
# ----------------------------------------------------------------------------
# To help define module version dependencies within branches, use the
# LockModuleVersion stanza to specify a version for a branch. This is override
# any 'dstm get' command for the defined module, effectively locking down its
# version.
# e.g. LockModuleVersion date 5 1-8
LockModuleVersion image 5 1-8
LockModuleVersion date 5 1-8
LockModuleVersion actions 5 2-4
LockModuleVersion workflow 5 2-2

# LOCK A THEME TO A SPECIFIC VERSION
# ----------------------------------------------------------------------------
# To help define theme version dependencies within branches, use the
# LockThemeVersion stanza to specify a version for a branch. This is override
# any 'dstm get' command for the defined module, effectively locking down its
# version.
# e.g. LockThemeVersion zen 5 1-1
_END_OF_SAMPLE_PROFILE_
			fi;
			status done;;
			
		update )
			if [ ! -e $PROFILE ]; then
				echo -n "I could not find an existing profile. ";
				echo "As a starting point, I can create a sample profile for you to edit.";
				echo -n "$(color bd)Should I create a sample profile (Yes|No) ? $(color off): ";
				read confirmation;
				if [ "$confirmation" == 'Yes' ]; then
					echo;
					profile sample;
				fi;
				echo;
			fi;
			status "Updating dstm profile...";
			$EDITOR $PROFILE 2>>$ERROR_LOG;
			# @todo $TIMESTAMP
			status done;;
		
		delete )
			if [[ -e $PROFILE && -z "$FORCE" ]]; then
				echo -n "I am about to delete your existing dstm profile. ";
				echo "Doing this removes my memory of default branches in your source tree, version dependencies, etc.";
				echo -n "$(color bd)Are you ready to continue (Yes|No) ? $(color off): ";
				read confirmation;
				if [ "$confirmation" != 'Yes' ]; then
					echo "$(color green)$(color bd)Cancelling$(color off)";
					exit 1;
				fi;
				echo;
			fi;
			status "Deleting dstm profile...";
			rm -fR $PROFILE 2>>$ERROR_LOG;
			status done;;
			
		show )
			status "This is your current dstm profile...";
			echo;
			echo;
			cat $PROFILE;;
			
		export )
			status "Exporting your dstm profile to $_file";
			cp $PROFILE $_file 2>>$ERROR_LOG;
			status done;;
		
		import )
			if [ -e $_file ]; then
				status "Importing $_file to your dstm profile";
				cp $_file $PROJECT 2>>$ERROR_LOG;
				status done;
			else
				status error "I need a file to import. Could you include the filename in the command next time. Thanks!";
			fi;;
			
	esac;
}

# Function: Read the local source tree to generate a profile
# Usage: backup [file]
# ----------------------------------------------------------------------------

function backup() {
	local _file=${1-$PWD/dstm-$_timestamp.backup};
	local _tag;
	local _version;
	local _revision;
	status "Backing up source tree description to $_file";
	cat > $_file <<_END_OF_HEADER_;
# ----------------------------------------------------------------------------
# @file Drupal source tree manager (dstm) backup
# Created by dstm backup as a description of the entire source tree.
# @created $TIMESTAMP
# ----------------------------------------------------------------------------
_END_OF_HEADER_
	cd $TREE_CORE;
	for i in `ls`; do
		echo "IncludeBranch $i" >> $_file;
		cd $TREE_MODULE/$i;
		for j in `ls`; do
			_tag=$(getCVStag module $i $j);
			_version=`echo $_tag | cut -d'-' -f4`;
			_revision=`echo $_tag | cut -d'-' -f5`;
			if [[ "$_revision" != '' && "$_tag" != 'HEAD' ]]; then
				_version=$_version'-'$_revision;
			fi;
			echo "LockModuleVersion $j $i $_version" >> $_file;
		done;
	done;
	status done;
}


# Function: Read a backup profile to build a local source tree
# Usage: restore [file]
# ----------------------------------------------------------------------------

function restore() {
	local _file=$PWD/$1;
	if [ -e $_file ]; then
		status "Restoring source tree from $_file";
		PROFILE=$_file;
		QUIET=1;
		FORCE=1;
		profileBranches;
		profileModules;
		build;
		QUIET=;
		status done;
	else
		status error "Unable to find $_file to restore from";
	fi;
}
# Function: Create a sample Makefile and Makefile.local for a Drupal project
# ----------------------------------------------------------------------------

function makefile() {
	status "Creating sample Makefile and Makefile.local for your Drupal project";
	cat > Makefile.sample <<'_END_OF_MAKEFILE_';
# ----------------------------------------------------------------------------
# @file
# Makefile to build a DOCUMENT_ROOT for your Drupal project.
# NOTE: Edit the parameters in your Makefile.local file before running 'make'
# ----------------------------------------------------------------------------

include ./Makefile.local

RSYNC := rsync -a -L --log-format='%f' --exclude '.svn' --exclude 'CVS' \
--exclude '.DS_Store' --exclude '*~' --exclude 'bluemarine' \
--exclude 'chameleon' --exclude 'pushbutton'

EXT := drupal drupal-contrib

sync: sync-ext

sync-ext: $(addprefix sync-,$(EXT))

sync-drupal: setup
	$(RSYNC) $(SRC_ROOT)/$(DRUPAL_SRC)/ $(TGT_ROOT)

sync-drupal-contrib: $(addprefix sync-drupal-contrib_,$(CONTRIB_MODULES))

sync-drupal-contrib_%: setup
	$(RSYNC) $(SRC_ROOT)/$(DRUPAL_CONTRIB_SRC)/$* \
	$(TGT_ROOT)/sites/all/modules/contrib/

setup:
	mkdir -p $(TGT_ROOT)/sites/all/modules/contrib
	mkdir -p $(TGT_ROOT)/sites/all/themes
	mkdir -p $(TGT_ROOT)/sites/all/backup
	mkdir -p $(TGT_ROOT)/sites/all/files

refresh: sync
_END_OF_MAKEFILE_
	cat > Makefile.local.sample <<_END_OF_MAKEFILE_LOCAL_;
# ----------------------------------------------------------------------------
# @file 
# Make parameters to customize your build
# @author $USER
# ----------------------------------------------------------------------------

#
# Base directory under which to find build trees
#
SRC_ROOT := /

#
# Document root of the web server you wish to build
#
TGT_ROOT := [Change to your chosen DOCUMENT_ROOT]

#
# The Drupal core tree you wish to build against 
#
DRUPAL_SRC := $TREE_CORE/[Add your branch here]

#
# The Drupal contrib tree you wish to build against
#
DRUPAL_CONTRIB_SRC := $TREE_MODULE/[Add your branch here]

#
# The Drupal contrib modules you'd like to build into this doc root
# This is a space delimited list of module project names,
# e.g. devel logintoboggan pngfix simplemenu
#
CONTRIB_MODULES :=	\
  [Add your project module names here]
_END_OF_MAKEFILE_LOCAL_
	status done;
}
# Function: Pull a context into the Drupal source tree
# Usage: get [core|module|theme] [project] [branch] [version]
# ----------------------------------------------------------------------------

function get() {
	local _context=${1:-$CONTEXT};
	local _project=${2:-$PROJECT};
	local _branch=${3:-$BRANCH};
	local _version=${4:-$VERSION};
	
	case "$_context" in
	
		core )
			if [ "$_branch" ]; then
				cvsCoCore $_branch;
			else
				if [ "$BRANCHES" ]; then
					for ((i=0;i<${#BRANCHES[@]};i++)); do
						cvsCoCore ${BRANCHES[${i}]};
					done;
				else 
					cvsCoCore;
				fi;
			fi;;
			
		module | theme )
		
			if [ "$_context" == 'module' ]; then _cvsCo=cvsCoModule;
			else _cvsCo=cvsCoTheme; fi;
			
			if [ "$_project" ]; then
				if [ "$_branch" ]; then
					$_cvsCo $_project $_branch $_version;
				else
					if [ "$BRANCHES" ]; then
						for ((i=0;i<${#BRANCHES[@]};i++)); do
							if [ `cat $PROFILE | egrep ^ExcludeModule | egrep $_project | egrep ${BRANCHES[${i}]} | wc -l | tr -d ' '` == '0' ]; then
								$_cvsCo $_project ${BRANCHES[${i}]} $_version;
							fi;
						done;
					else 
						$_cvsCo $_project;
					fi;
				fi;
			else
				if [ "$MODULES" ]; then
					for ((i=0;i<${#MODULES[@]};i++)); do
						if [ "$BRANCHES" ]; then
							for ((j=0;j<${#BRANCHES[@]};j++)); do
								if [ `cat $PROFILE | egrep ^ExcludeModule | egrep ${MODULES[${i}]} | egrep ${BRANCHES[${j}]} | wc -l | tr -d ' '` == '0' ]; then
									$_cvsCo ${MODULES[${i}]} ${BRANCHES[${j}]} $_version;
								fi;
							done;
						else 
							$_cvsCo ${MODULES[${i}]};
						fi;
					done;
				else 
					$_cvsCo $_project;
				fi;
			fi;;
						
		profile )
			profile sample;;
			
	esac;
}

# Function: Update a context within the Drupal source tree
# Usage: update [core|all] [branch]
# Usage: update [module|theme|all] [project|all] [branch]
# ----------------------------------------------------------------------------

function update() {
	local _context=${1:-$CONTEXT};
	local _project=${2:-$PROJECT};
	local _branch=${3:-$BRANCH};
	
	case "$_context" in
	
		'' | all )
			update core;
			update module;
			#update theme;
			;;
			
		core )
			cd $TREE_CORE;
			case "$_branch" in
				'' | all )
					for _dir in `ls`; do
						cvsUpCore $_dir; 
					done;;
				* )
					cvsUpCore $_branch;;	
			esac;;
			
		module | theme )
			if [ "$_context" == 'module' ]; then 
				_dir=$TREE_MODULE;
				_cvsCo=cvsCoModule;
			else 
				_dir=$TREE_THEME;
				_cvsCo=cvsCoTheme; 
			fi;

			cd $_dir;
			case "$_project" in
				'' | all )
					if [[ -d $_branch && "$_branch" ]]; then
						cd $_branch;
						for _project in `ls`; do
							$_cvsCo $_project $_branch;
						done;
					else
						for _i in `ls`; do
							cd $_dir/$_i;
							for _project in `ls`; do
								$_cvsCo $_project $_i;
							done;
						done;
					fi;;	
				* )
					if [ "$_branch" ]; then
						$_cvsCo $_project $_branch;
					else
						for _i in `find . | egrep $_project\\.info | cut -d'/' -f2`; do
							$_cvsCo $_project $_i; 
						done;
					fi;; 
			esac;;
			
		profile )
			profile update;;
			
	esac;
}
# Function: Delete a context within the Drupal source tree
# Usage: delete [core|module|theme] [project] [branch]
# ----------------------------------------------------------------------------

function delete() {
	local _context=${1:-$CONTEXT};
	local _project=${2:-$PROJECT};
	local _branch=${3:-$BRANCH};
	
	case "$_context" in
	
		'' | all )
			delete core;
			delete module;
			#delete theme;
			;;
			
		core )
			cd $TREE_CORE;
			case "$_branch" in
				'' | all )
					status "Deleting all core";
					rm -fR ./* 2>>$ERROR_LOG;
					status done;;
				* )
					if [ "$_branch" == 'HEAD' ]; then
						status "Deleting core $_branch";
					else
						status "Deleting DRUPAL-$_branch";
					fi;
					rm -fR ./$_branch 2>>$ERROR_LOG;
					status done;;
			esac;;
			
		module | theme )
			if [ "$_context" == 'module' ]; then 
				_dir=$TREE_MODULE;
			else 
				_dir=$TREE_THEME;
			fi;
			
			cd $_dir;
			case "$_project" in
				'' | all )
					if [[ -d $_branch && "$_branch" ]]; then
						status "Deleting all ${_context}s from $_branch";
						rm -fR ./$_branch 2>>$ERROR_LOG;
					else
						status "Deleting all ${_context}s";
						rm -fR ./ 2>>$ERROR_LOG;
					fi;
					status done;;
				* )
					if [ "$_branch" ]; then
						if [ "$_branch" == 'HEAD' ]; then
							status "Deleting $_project $_context ($_branch)";
						else
							status "Deleting $_project $_context (DRUPAL-$_branch)";
						fi;
						rm -fR ./$_branch/$_project 2>>$ERROR_LOG;
						status done;
					else
						for _dir in `find . | egrep $_project\\.info | cut -d'/' -f2`; do
							if [ "$_dir" == 'HEAD' ]; then
								status "Deleting $_project $_context ($_dir)";
							else
								status "Deleting $_project $_context (DRUPAL-$_dir)";
							fi;
							rm -fR ./$_dir/$_project 2>>$ERROR_LOG;
							status done;
						done;
					fi;;
			esac;;
			
		profile )
			profile delete;;
			
	esac;
}
# Function: Information for a context within the Drupal source tree
# Usage: info [core|all] [branch|-]
# Usage: info [module|theme|all] [project|-] [branch]
# ----------------------------------------------------------------------------

function info() {
	local _context=${1:-$CONTEXT};
	local _project;
	local _branch;
	local _line;
	
	if [[ "$_context" == "core" ]]; then
		_branch=${2:-$BRANCH};
	else
		_project=${2:-$PROJECT};
		_branch=${3:-$BRANCH};
	fi;
	
	case "$_context" in
	
		'' | all )
			info core ${2:-$BRANCH};
			info module ${2:-$PROJECT} ${3:-$BRANCH};
			info theme ${2:-$PROJECT} ${3:-$BRANCH};
			;;
			
		core )
			cd $TREE_CORE;
			echo;
			echo "$(color bd)Drupal core...$(color)";
			case "$_branch" in
				'' | - )
					for _dir in `ls`; do
						getCVStag core $_dir; echo;
					done;;
				* )
					getCVStag core $_branch; echo;
			esac;;
			
		module | theme )
			if [ "$_context" == 'module' ]; then
				_dir=$TREE_MODULE;
			else 
				_dir=$TREE_THEME;
			fi;
			cd $_dir;
			
			echo;
			echo "$(color bd)Drupal $_context contrib...$(color)";
			echo;
			echo "$(color bd)Name              Branch  Tag            Description$(color)";
			_line="%-15s   %-5s   %-13s  $(color cyan)%-22s$(color)\n";
			case "$_project" in
				'' | - )
					if [[ -d $_branch && "$_branch" ]]; then
						cd $_branch;
						for _project in `ls`; do
							printf "$_line" $_project $_branch $(getCVStag $_context $_branch $_project) "$(getInfoFileDescription $_context $_branch $_project)";
						done;
					else
						for _i in `ls`; do
							cd $_dir/$_i;
							for _project in `ls`; do
								printf "$_line" $_project $_i $(getCVStag $_context $_i $_project) "$(getInfoFileDescription $_context $_i $_project)";
							done;
						done;
					fi;;	
				* )
					if [ "$_branch" ]; then
						printf "$_line" $_project $_branch $(getCVStag $_context $_branch $_project) "$(getInfoFileDescription $_context $_branch $_project)";

					else
						for _i in `find . | egrep $_project\\.info | cut -d'/' -f2`; do
							printf "$_line" $_project $_i $(getCVStag $_context $_i $_project) "$(getInfoFileDescription $_context $_i $_project)";
						done;
					fi;; 
			esac;;
			
		profile )
			profile show;;
			
	esac;
}
# Function: Check out Drupal core
# Usage: cvsCoCore [branch]
# ----------------------------------------------------------------------------

function cvsCoCore() {
	local _tag;
	local _dir;
	
	case "$1" in
		HEAD | '' ) _tag=HEAD; _dir=HEAD;;
		* )					_tag=DRUPAL-$1; _dir=$1;;
	esac;
	
	cd $TREE_CORE;
	status "Fetching $_tag";
	cvs $CVS_REPO -Q co -d $_dir -r $_tag drupal 2>>$ERROR_LOG;
	status done;
}

# Function: Check out Drupal module project
# Usage: cvsCoModule project [branch] [version]
# ----------------------------------------------------------------------------

function cvsCoModule() {
	local _project=${1:-devel};
	local _branch=${2:-HEAD};
	local _version=$3;
	
	if [[ -e $PROFILE && `cat $PROFILE | egrep ^LockModuleVersion | egrep $_project | egrep $_branch | wc -l | tr -d ' '` != '0' ]]; then
		_version=`cat $PROFILE | egrep ^LockModuleVersion | egrep $_project | egrep $_branch | awk {'print $4'}`;
	fi;

	if [ ! -d $TREE_MODULE/$_branch ]; then 
		mkdir -p $TREE_MODULE/$_branch;
	fi;
	cd $TREE_MODULE/$_branch;
	
	local _tag;
	case "$_branch" in
		HEAD )	_tag=HEAD; _dir=HEAD;;
		* )			_tag=DRUPAL-$_branch; _dir=$_project;;
	esac;
	
	if [ "$_version" ]; then
		# @todo _version =~ s/./-/g
		_tag=$_tag--$_version;
	fi;
	
	status "Fetching $_project module ($_tag)";

	if [ "$FORCE" ]; then
		rm -fR $_project;
	fi;
	
	if [ -d $_project ]; then 
		status "already exists locally";
		status done;
		return;
	fi;

	if [ "$_version" ]; then
	
		# Check out a specific version...
	
		status " ($_tag)";
		cvs $CVS_CONTRIB_REPO -Q co -d $_project -r $_tag $CVS_MODULE_REPO_DIR/$_project
		if [ ! -d $_project ]; then
			status "unable to checkout this module project";
			status done;
			return;
		fi;
	
	else
	
		# Find latest version...
		
		local _checkout_by_version=0
		local _checkout_by_branch=1
		local _checkout_head=2
		local _project_not_found;
		local _tag_not_found;
		local _try_tag;
		local _exit=NO;
		local _log=.tmp_log;
		
		touch $_log;
		
		if [ "$_tag" == 'HEAD' ]; then _checkout=$_checkout_head;
		else _checkout=$_checkout_by_version;
		fi;
		
		until [[ -d $_project || "$_project_not_found" != '' || "$_project_disappeared" != '' || "$_exit" == 'YES' ]]; do
		
			case "$_checkout" in
			
				"$_checkout_by_version" )
					if [ -z "$_try_tag" ]; then
						_version=8;	# not spotted any versions higher than 7
						_try_tag=$_tag--$_version;
					fi;
					if (("$_version" > "1")); then
						_version=$[$_version -1];
						_try_tag=$_tag--$_version;
					else 
						_checkout=$_checkout_by_branch;
					fi;
					;;
					
				"$_checkout_by_branch" )
					if [ "$_try_tag" == "$_tag" ]; then
						_checkout=$_checkout_head;
					else
						_try_tag=$_tag;
					fi;
					;;
					
				"$_checkout_head" )
				  if [ "$_try_tag" == 'HEAD' ]; then
						_exit=YES;
					else
						_try_tag=HEAD;
					fi;
				  ;;
				  
				* )
					_exit=YES;
					;;
			esac;
			
			cvs $CVS_CONTRIB_REPO -Q co -d $_project -r $_try_tag $CVS_MODULE_REPO_DIR/$_project 2>$_log;
			
			_project_not_found=`cat $_log | grep 'could not read RCS file'`;
			_project_disappeared=`cat $_log | grep 'has disappeared'`;
			_tag_not_found=`cat $_log | grep 'no such tag'`;
			
			if [[ "$VERBOSE" && -z "$QUIET" ]]; then
				echo -n '.';
			fi;
			
		done;
	
		cat $_log >> $ERROR_LOG;
		rm $_log;
		
		if [[ "$_project_not_found" != '' || "$_project_disappeared" != '' ]]; then
			status "unable to find this module project";
			status done;
			return;
		fi;
		
		if [[ "$VERBOSE" && -z "$QUIET" ]]; then
			echo -n "$_try_tag";
		fi;
		
	fi;
	

	status done;
}

# Function: Check out Drupal theme project
# Usage: cvsCoTheme project [branch] [version]
# ----------------------------------------------------------------------------

function cvsCoTheme() {
	local _project=${1:-devel};
	local _branch=${2:-HEAD};
	local _version=$3;
	
	if [[ -e $PROFILE && `cat $PROFILE | egrep ^LockThemeVersion | egrep $_project | egrep $_branch | wc -l | tr -d ' '` != '0' ]]; then
		_version=`cat $PROFILE | egrep ^LockThemeVersion | egrep $_project | egrep $_branch | awk {'print $4'}`;
	fi;

	if [ ! -d $TREE_THEME/$_branch ]; then 
		mkdir -p $TREE_THEME/$_branch;
	fi;
	cd $TREE_THEME/$_branch;
	
	local _tag;
	case "$_branch" in
		HEAD )	_tag=HEAD; _dir=HEAD;;
		* )			_tag=DRUPAL-$_branch; _dir=$_project;;
	esac;
	
	if [ "$_version" ]; then
		# @todo _version =~ s/./-/g
		_tag=$_tag--$_version;
	fi;
	
	status "Fetching $_project theme ($_tag)";

	if [ "$FORCE" ]; then
		rm -fR $_project;
	fi;
	
	if [ -d $_project ]; then 
		status "already exists locally";
		status done;
		return;
	fi;

	if [ "$_version" ]; then
	
		# Check out a specific version...
	
		status " ($_tag)";
		cvs $CVS_CONTRIB_REPO -Q co -d $_project -r $_tag $CVS_THEME_REPO_DIR/$_project
		if [ ! -d $_project ]; then
			status "unable to checkout this theme project";
			status done;
			return;
		fi;
	
	else
	
		# Find latest version...
		
		local _checkout_by_version=0
		local _checkout_by_branch=1
		local _checkout_head=2
		local _project_not_found;
		local _tag_not_found;
		local _try_tag;
		local _exit=NO;
		local _log=.tmp_log;
		
		touch $_log;
		
		if [ "$_tag" == 'HEAD' ]; then _checkout=$_checkout_head;
		else _checkout=$_checkout_by_version;
		fi;
		
		until [[ -d $_project || "$_project_not_found" != '' || "$_project_disappeared" != '' || "$_exit" == 'YES' ]]; do
		
			case "$_checkout" in
			
				"$_checkout_by_version" )
					if [ -z "$_try_tag" ]; then
						_version=8;	# not spotted any versions higher than 7
						_try_tag=$_tag--$_version;
					fi;
					if (("$_version" > "1")); then
						_version=$[$_version -1];
						_try_tag=$_tag--$_version;
					else 
						_checkout=$_checkout_by_branch;
					fi;
					;;
					
				"$_checkout_by_branch" )
					if [ "$_try_tag" == "$_tag" ]; then
						_checkout=$_checkout_head;
					else
						_try_tag=$_tag;
					fi;
					;;
					
				"$_checkout_head" )
				  if [ "$_try_tag" == 'HEAD' ]; then
						_exit=YES;
					else
						_try_tag=HEAD;
					fi;
				  ;;
				  
				* )
					_exit=YES;
					;;
			esac;
			
			cvs $CVS_CONTRIB_REPO -Q co -d $_project -r $_try_tag $CVS_THEME_REPO_DIR/$_project 2>$_log;
			
			_project_not_found=`cat $_log | grep 'could not read RCS file'`;
			_project_disappeared=`cat $_log | grep 'has disappeared'`;
			_tag_not_found=`cat $_log | grep 'no such tag'`;
			
			if [[ "$VERBOSE" && -z "$QUIET" ]]; then
				echo -n '.';
			fi;
			
		done;
	
		cat $_log >> $ERROR_LOG;
		rm $_log;
		
		if [[ "$_project_not_found" != '' || "$_project_disappeared" != '' ]]; then
			status "unable to find this theme project";
			status done;
			return;
		fi;
		
		if [[ "$VERBOSE" && -z "$QUIET" ]]; then
			echo -n "$_try_tag";
		fi;
		
	fi;
	
	status done;
}

# Function: Update Drupal core
# Usage: cvsUpCore [branch]
# ----------------------------------------------------------------------------

function cvsUpCore() {
	local _tag;
	local _dir;
	
	case "$1" in
		HEAD | '' ) _tag=HEAD; _dir=HEAD;;
		* )					_tag=DRUPAL-$1; _dir=$1;;
	esac;
	
	if [ -d $TREE_CORE/$_dir ]; then
		cd $TREE_CORE/$_dir;
		status "Updating $_tag";
		cvs -Q up -d -P 2>>$ERROR_LOG;
		status done;
	else
		status error "Oops! It seems core $_dir does not exist. Perhaps you should use the dstm get core $_dir command.";
	fi;
}

# Function: Update Drupal module project
# Usage: cvsUpModule [project] [branch]
# ----------------------------------------------------------------------------

function cvsUpModule() {
	local _project=${1:-devel};
	local _branch=${2:-HEAD};
	
	if [ -d $TREE_MODULE/$_branch/$_project ]; then
		cd $TREE_MODULE/$_branch/$_project;
		status "Updating $_project module ($_branch)";
		cvs -Q up -d -P 2>>$ERROR_LOG;
		status done;
	else
		status error "Oops! It seems module $_project $_branch does not exist. Perhaps you should use the dstm update module $_project $_branch command.";
	fi;
}

# Function: Update Drupal theme project
# Usage: cvsUpTheme [project] [branch]
# ----------------------------------------------------------------------------

function cvsUpTheme() {
	local _project=${1:-devel};
	local _branch=${2:-HEAD};
	
	if [ -d $TREE_THEME/$_branch/$_project ]; then
		cd $TREE_THEME/$_branch/$_project;
		status "Updating $_project theme ($_branch)";
		cvs -Q up -d -P 2>>$ERROR_LOG;
		status done;
	else
		status error "Oops! It seems theme $_project $_branch does not exist. Perhaps you should use the dstm update theme $_project $_branch command.";
	fi;
}
# Pre process
# ----------------------------------------------------------------------------

profileBranches;
profileModules;
	
# Parse input arguments
# ----------------------------------------------------------------------------

if [ -z "$1" ]; then help; fi;

while (( "$#" )); do

	case "$1" in
		
		# Options
		-q | --quiet )		QUIET=1;;
		-v | --verbose )	VERBOSE=1;;
		-f | --force )		FORCE=1;;
		
		# General actions
		help )						help;;
		init )  					clean; profile sample; build;;
		clean )						clean;;
		build )						build;;
		backup )					backup;;
		makefile )				makefile;;
		
		# Context for an action
		core | C )				CONTEXT=core;;
		module | M )			CONTEXT=module;;
		theme | T )				CONTEXT=theme;;
		profile )					CONTEXT=profile;;
		all )							CONTEXT=all;;
		
		# Action based on context
		retrieve | get )	ACTION=get;;
		update | up )			ACTION=update;;
		delete | del )		ACTION=delete;;
		info | i )				ACTION=info;;
		import | im )			ACTION=import;;
		export | ex )			ACTION=export;;
		restore )					ACTION=restore;;
		
		# Parse project, branch and version based on context
		*)
			case "$CONTEXT" in
					
				core )
					if [ -z "$BRANCH" ]; then 
						BRANCH=$1;
					else status error "The core context only takes the branch argument.";
					fi;;
					
				module | theme )
					if [ -z "$PROJECT" ]; then 
						PROJECT=$1; 
					else 
						if [ -z "$BRANCH" ]; then 
							BRANCH=$1;
						else 
							if [ -z "$VERSION" ]; then 
								VERSION=$1;
							else status error "The module and theme context only take the project, branch and version arguments.";
							fi;
						fi;
					fi;;
				
				profile | restore )
					if [ -z "$FILE" ]; then
						FILE=$1;
					fi;;
					
			esac; # case $CONTEXT
			
			case "$ACTION" in
			
				restore )
					if [ -z "$FILE" ]; then
						FILE=$1;
					fi;;
					
			esac;; # case $ACTION
				
			
	esac # case $1
	
	shift;

done;

# Invoke and action
# ----------------------------------------------------------------------------

case "$ACTION" in
	get )			get $CONTEXT $PROJECT $BRANCH $VERSION;;
	update )	update $CONTEXT $PROJECT $BRANCH;;
	delete )	delete $CONTEXT $PROJECT $BRANCH;;
	info )		info $CONTEXT $PROJECT $BRANCH;;
	import )  profile import $FILE;;
	export )  profile export;;
	restore ) restore $FILE;;
esac
